﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using Jacaranda.Data;
using System.Data;
using System.Reflection;

namespace Jacaranda.Model
{
    public class AssociationField
    {
        public AssociationType AssociationType { get; set; }

        public Type From { get; set; }

        public Type To { get; set; }

        private string propertyName;
        public string PropertyName
        {
            get { return propertyName; }
            set
            {
                propertyName = value;
                FieldName = ToCamelNotation(propertyName);
            }
        }

        public string FieldName { get; set; }

        public EntityAssociation Association { get; set; }

        public bool IsSourceToTargetOrder { get; set; }


        private Entity GetAssociationSingle(Entity entity)
        {
            return entity.GetFieldInfo(FieldName).GetValue(entity) as Entity;
        }

        private EntityList GetAssociationList(Entity entity)
        {
            return entity.GetFieldInfo(FieldName).GetValue(entity) as EntityList;
        }


        public void SetAssociationsFromField(Entity entity, object value)
        {
            entity.GetFieldInfo(FieldName).SetValue(entity, value);
        }

        private static string ToCamelNotation(string identifier)
        {
            return identifier.Substring(0, 1).ToLower() + identifier.Substring(1);
        }

        internal int GetTotalCount(Entity entity)
        {
            FieldMetadata key = Metadata.Type(entity.GetType()).PrimaryKey;

            if (IsSourceToTargetOrder)
            {
                return Database.Count(To, Database.In(Association.From, Association.To, new AbstractProperty(key.PropertyName) == key.GetValue(entity), null));
            }
            else
            {
                return Database.Count(From, Database.In(Association.From, Association.To, null, new AbstractProperty(key.PropertyName) == key.GetValue(entity)));
            }
        }

        internal Entity[] Load(Entity entity, int pageIndex, int pageSize)
        {
            SearchPageCriteria page = new SearchPageCriteria() { PageIndex = pageIndex, PageSize = pageSize };
            FieldMetadata key = Metadata.Type(entity.GetType()).PrimaryKey;

            if (IsSourceToTargetOrder)
            {
                return Database.Search(To,
                    Database.In(Association.From, Association.To, new AbstractProperty(key.PropertyName) == key.GetValue(entity), null)
                    , null, page, null);
            }
            else
            {
                return Database.Search(From,
                    Database.In(Association.From, Association.To, null, new AbstractProperty(key.PropertyName) == key.GetValue(entity))
                    , null, page, null);
            }
        }

        internal List<Entity> Load(Entity entity)
        {
            return Load(entity, null);
        }

        internal List<Entity> Load(Entity entity, Transaction tran)
        {
            DataTable associationEntityTable = null;

            foreach (EntityMetadata toType in Metadata.Type(To).RelatedEntities)
            {
                string sql = string.Format("SELECT * FROM {0} WHERE {1} IN (SELECT {2} FROM {3} WHERE {4}='{5}')",
                    toType.TableName,
                    toType.PrimaryKey.FieldName,
                    (IsSourceToTargetOrder ? "Key2" : "Key1"),
                    Association.TableName,
                    (IsSourceToTargetOrder ? "Key1" : "Key2"),
                    Metadata.Type(entity.GetType()).PrimaryKey.GetValue(entity));

                associationEntityTable = (tran == null) ? DataPortal.Select(sql) : DataPortal.Select(sql, tran);

                if (associationEntityTable.Rows.Count > 0)
                {
                    break;
                }
            }

            return Mapper.RelationToEntity(associationEntityTable, To);
        }

        internal void Update(Entity entity, Transaction transaction)
        {
            if (AssociationType == AssociationType.OneToOne)
            {
                Entity association = GetAssociationSingle(entity);

                Delete(entity, transaction);

                if (association != null)
                {
                    Insert(entity, association, transaction);
                }

                return;
            }
            

            EntityList associations = GetAssociationList(entity);

            if (associations == null)
            {
                Delete(entity, transaction);
                return;
            }
            else if (!associations.IsChanged)
            {
                return;
            }
            else
            {
                if (associations.IsCleared)
                {
                    Delete(entity, transaction);
                }

                if (associations.Removed != null)
                {
                    associations.Removed.ForEach(i => Delete(entity, i, transaction));
                }

                if (associations.Added != null)
                {
                    associations.Added.ForEach(i => Insert(entity, i, transaction));
                }
            }
        }

        private void Delete(Entity entity, Transaction transaction)
        {
            DataPortal.Execute(string.Format("DELETE FROM {0} WHERE {1}='{2}'",
                          Association.TableName,
                          (IsSourceToTargetOrder ? "Key1" : "Key2"),
                          Metadata.Type(entity.GetType()).PrimaryKey.GetValue(entity)), transaction);
        }

        private void Delete(Entity entity1, Entity entity2, Transaction transaction)
        {
            Entity source = entity1;
            Entity target = entity2;

            if (!IsSourceToTargetOrder)
            {
                source = entity2;
                target = entity1;
            }

            Association.Delete(source, target, transaction);
        }

        private void Insert(Entity entity1, Entity entity2, Transaction transaction)
        {
            Entity source = entity1;
            Entity target = entity2;

            if (!IsSourceToTargetOrder)
            {
                source = entity2;
                target = entity1;
            }

            Association.Insert(source, target, transaction);
        }
    }
}
